/*
 * AffixHierarchy.java
 * Created on November 5, 2007, 7:59 AM
 *
 */
package org.osdtsystem.hierarchy;

import java.util.Arrays;
import java.util.List;
import org.osdtsystem.dataset.Classification;
import org.osdtsystem.dataset.ClassificationEngine;
import org.osdtsystem.dataset.Dataset;
import org.osdtsystem.dataset.HierarchyView;
import org.osdtsystem.utils.Storage;

/**
 * An abstract affix hierarchy.
 * @author Martin Haulrich and Matthias Buch-Kromann
 */
public abstract class AbstractAffixHierarchy extends AbstractSimpleHierarchy<AffixHClass> {
    // Fields
    private String TOPCLASSNAME = "TOP";
    private String EMPTYCLASSNAME = "EMPTY";
    private static AffixHClass[] emptyAffixHClassArray = new AffixHClass[0];

    // Constructors
    public AbstractAffixHierarchy(Storage storage) {
        super(AffixHClass.class, storage);
    }

    // Abstract methods
    /**
     * The super class for an affix at a given depth.
     * @param affix the affix
     * @param depth the depth of the super class
     * @return the super class, or <code>null</code> if it doesn't exist
     */
    public abstract AffixHClass superclass(String affix, int depth);

    @Override
    public final AffixHClass superclass(AffixHClass hclass, int depth) {
        return superclass(hclass.affix(), depth);
    }

    /**
     * Terminal or non-terminal affix class for a given affix. If an affix class
     * with affix A is terminal, it cannot dominate affixes with larger depth.
     * @param affix the affix
     * @param terminal <code>true</code> if the affix is terminal, <code>false</code>
     * otherwise
     * @return the affix class
     */
    public final AffixHClass makeAffixClass(String affix, boolean terminal) {
        return new AffixHClass(affix, terminal);
    }


    @Override
    public final String className(AffixHClass hclass) {
        if (isTop(hclass))
            return TOPCLASSNAME;
        if (isEmpty(hclass))
            return EMPTYCLASSNAME;
        return hclass.affix();
    }

//    @Override
//    public AffixHClass[] subclasses(AffixHClass hclass) {
//        return null;
//    }

    @Override
    final AffixHClass makeEmptyClass() {
        return AffixHClass.emptyClass;
    }

    @Override
    final AffixHClass makeTopClass() {
        return AffixHClass.topClass;
    }

    @Override
    public AffixHClass dataClass(HierarchyView<AffixHClass> view, Dataset dataset, int id) {
        return makeAffixClass(view.feature(dataset, id), true);
    }

    public AffixHClass[] dataClasses(HierarchyView view, Dataset dataset, int identifier, AffixHClass root,
            int maxDepthBelowRoot) {
        String suffix = view.feature(dataset, identifier);
        int minDepth = root.depth() + 1;
        int maxDepth = minDepth + maxDepthBelowRoot - 1;
        return superclasses(suffix, minDepth, maxDepth);
    }

    public AffixHClass[] superclasses(AffixHClass hclass, int minDepth, int maxDepth) {
        return superclasses(
                hclass.isTerminal()
                ? hclass.affix()
                : superclass(hclass.affix(), hclass.depth() - 1).affix(), minDepth, maxDepth);
    }

    public final AffixHClass[] superclasses(String affix, int minDepth, int maxDepth) {
        // Ensure that min and max depths are within valid range
        int offset = Math.max(minDepth, 0);
        int n = Math.max(0, Math.min(maxDepth, affix.length()) - offset);

        // Construct super classes
        AffixHClass[] list = new AffixHClass[n];
        for (int i = 0; i < n; ++i) {
            list[i] = superclass(affix, offset + i);
        }

        // Return super classes
        return list;
    }

    @Override
    public List<AffixHClass> superclasses(AffixHClass hclass) {
        return Arrays.asList(superclasses(hclass, hclass.depth(), hclass.depth() - 1));
    }

    @Override
    public List<AffixHClass> superclassesBelowRoot(AffixHClass hclass,
            AffixHClass root, int minDepthBelowRoot, int maxDepthBelowRoot) {
        return Arrays.asList(_superclassesBelowRoot(hclass, root, minDepthBelowRoot, maxDepthBelowRoot));
    }
    
    AffixHClass[] _superclassesBelowRoot(AffixHClass hclass,
            AffixHClass root, int minDepthBelowRoot, int maxDepthBelowRoot) {
        // Return empty array if root class does not contain hclass
        if (! contains(root, hclass))
            return emptyAffixHClassArray;

        // Otherwise return all superclasses within the depth bounds
        int minDepth = root.depth() + minDepthBelowRoot;
        int maxDepth = Math.min(
                root.depth() + maxDepthBelowRoot,
                hclass.isTerminal() ? hclass.depth() : hclass.depth() - 1);
        return superclasses(hclass.affix(), minDepth, maxDepth);
    }

    @Override
    public List<AffixHClass> superclassesBelowRoot(HierarchyView<AffixHClass> view, Dataset dataset, int identifier, AffixHClass root,
            int minDepthBelowRoot, int maxDepthBelowRoot) {
        return Arrays.asList(_superclassesBelowRoot(view, dataset, identifier, root, minDepthBelowRoot, maxDepthBelowRoot));
    }

    AffixHClass[] _superclassesBelowRoot(HierarchyView<AffixHClass> view, Dataset dataset, int identifier, AffixHClass root,
            int minDepthBelowRoot, int maxDepthBelowRoot) {
        return _superclassesBelowRoot(dataClass(view, dataset, identifier),
                root, minDepthBelowRoot, maxDepthBelowRoot);
    }

    @Override
    public Classification<AffixHClass> classify(AffixHClass root,HierarchyView<AffixHClass> view, Dataset dataset) {
        // Create classification
        Classification<AffixHClass> classification
                = new ClassificationEngine<AffixHClass>(storageAbstractFactory, this, root);

        // Iterate through the dataset, saving all super classes to specified depth
        for (Integer identifier : dataset) {
            for (AffixHClass hclass : _superclassesBelowRoot(view, dataset, identifier, root, 1,
                    classification.parameters().maximalClassDepth())) {
                classification.addDataPoint(hclass, identifier);
            }
        }

        // Clean up the classification
        classification.trim();
        return classification;
    }
}
